package org.fpp.sql.convert.atlas;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.fpp.sqlparse.ISqlParseSupport;
import org.fpp.sqlparse.SqlParseSupport;
import org.fpp.sqlparse.TableInfo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
 * @author bigbird-0101
 * @date 2024-03-29 15:42
 */
public class SqlToAtlasModelConverter {

    private final ISqlParseSupport sqlParseSupport=new SqlParseSupport();

    public void convert(File source,File targetFileDirectory,String fileNamePrefix){
        Map<String, TableInfo> stringTableInfoMap;
        try {
            stringTableInfoMap = sqlParseSupport.analysisDdl(IoUtil.readUtf8(new FileInputStream(source)));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        Set<String> strings = stringTableInfoMap.keySet()
                .stream()
                .map(s-> StrUtil.split(s,"_").get(0))
                .collect(Collectors.toSet());
        Map<String,List<TableInfo>> listMap=new HashMap<>();
        for (String string : strings) {
            for (String b : stringTableInfoMap.keySet()) {
                if (b.startsWith(string)) {
                    if (!listMap.containsKey(string)) {
                        List<TableInfo> tableInfos = new ArrayList<>();
                        tableInfos.add(stringTableInfoMap.get(b));
                        listMap.put(string, tableInfos);
                    } else {
                        listMap.get(string).add(stringTableInfoMap.get(b));
                    }
                }
            }
        }
        Map<String,Model> map=new HashMap<>();
        for (Map.Entry<String, List<TableInfo>> entry : listMap.entrySet()) {
            String serviceType = entry.getKey();
            List<EntityDef> collect = entry.getValue()
                    .stream()
                    .map(s -> tableInfoConvertEntityDef(s, serviceType))
                    .collect(toList());
            Model model=Model.defaultObj();
            EntityDef bridgeProcess = buildBridgeProcessEntityDef(serviceType);
            collect.add(bridgeProcess);
            model.setEntityDefs(collect);
            model.setRelationshipDefs(entityDefConvertRelationshipDef(serviceType,collect));
            map.put(serviceType,model);
        }
        int currentFilePrefixNumber;
        for (Map.Entry<String, Model> entry : map.entrySet()) {
            String serviceType = entry.getKey();
            Model v = entry.getValue();
            File file;
            if(NumberUtil.isNumber(fileNamePrefix)) {
                currentFilePrefixNumber = Convert.toInt(fileNamePrefix) + 1;
                String newFilePrefix=String.valueOf(currentFilePrefixNumber);
                file=new File(targetFileDirectory,newFilePrefix+"-"+serviceType+"_model.json");
            }else{
                file=new File(targetFileDirectory,serviceType+"_model.json");
            }
            if(file.exists()){
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            try {
                IoUtil.writeUtf8(new FileOutputStream(file),true, JSONUtil.toJsonPrettyStr(v));
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private List<RelationshipDef> entityDefConvertRelationshipDef(String serviceType, List<EntityDef> entityDefs){
        List<Pair<EntityDef, EntityDef>> pairs = new ArrayList<>();
        List<EntityDef> newEntityDefs=entityDefs.stream()
                .filter(s->!s.getSuperTypes().contains("Process"))
                .collect(toList());
        for (int i = 0; i < newEntityDefs.size(); i++) {
            for (int j = i + 1; j < newEntityDefs.size(); j++) {
                pairs.add(new Pair<>(newEntityDefs.get(i), newEntityDefs.get(j)));
            }
        }
        List<RelationshipDef> relationshipDefs=new ArrayList<>();
        for (Pair<EntityDef, EntityDef> pair : pairs) {
            EntityDef key = pair.getKey();
            EntityDef value = pair.getValue();
            RelationshipDef relationshipDef = getRelationshipDef(serviceType, key, value);
            relationshipDefs.add(relationshipDef);
        }
        return relationshipDefs;
    }

    private RelationshipDef getRelationshipDef(String serviceType, EntityDef source, EntityDef target) {
        RelationshipDef relationshipDef=new RelationshipDef();
        relationshipDef.setName(serviceType +"_model_"+ source.getName()+"_"+ target.getName());
        relationshipDef.setTypeVersion("1.0");
        relationshipDef.setPropagateTags("NONE");
        relationshipDef.setRelationshipCategory("ASSOCIATION");
        relationshipDef.setRelationshipLabel("__"+ serviceType +"_model."+ source.getName()+"."+ target.getName());
        RelationshipDef.EndDefDTO endDef1 = new RelationshipDef.EndDefDTO();
        endDef1.setType(source.getName());
        endDef1.setName(target.getName());
        endDef1.setCardinality("SINGLE");
        endDef1.setIsLegacyAttribute(true);
        endDef1.setIsContainer(false);
        relationshipDef.setEndDef1(endDef1);
        RelationshipDef.EndDefDTO endDef2 = new RelationshipDef.EndDefDTO();
        endDef2.setType(target.getName());
        endDef2.setName(source.getName());
        endDef2.setCardinality("SINGLE");
        endDef2.setIsLegacyAttribute(true);
        endDef2.setIsContainer(false);
        relationshipDef.setEndDef2(endDef2);
        return relationshipDef;
    }

    private EntityDef tableInfoConvertEntityDef(TableInfo tableInfo, String serviceType) {
        String name = tableInfo.getName();
        EntityDef entityDef = new EntityDef();
        entityDef.setName(name);
        entityDef.setDescription(tableInfo.getComment());
        entityDef.setSuperTypes(List.of("DataSet"));
        entityDef.setServiceType(serviceType);
        entityDef.setTypeVersion("1.1");
        entityDef.setAttributeDefs(new ArrayList<>());
        for (TableInfo.ColumnInfo columnInfo : tableInfo.getColumnList()) {
            EntityDef.AttributeDefsDTO attributeDefsDTO = new EntityDef.AttributeDefsDTO();
            attributeDefsDTO.setName(columnInfo.getName());
            attributeDefsDTO.setCardinality("SINGLE");
            attributeDefsDTO.setIsOptional(true);
            attributeDefsDTO.setIsUnique(false);
            attributeDefsDTO.setIsIndexable(tableInfo.getIndexList().stream().anyMatch(s -> s.getColumnNames().contains(columnInfo.getName())));
            attributeDefsDTO.setTypeName(mappingType(columnInfo.getDataType()));
            entityDef.getAttributeDefs().add(attributeDefsDTO);
        }
        entityDef.getAttributeDefs().addAll(buildCommonAttribute());
        return entityDef;
    }

    private String mappingType(String type){
        switch (type){
            case "VARCHAR":
            case "varchar":
                return "string";
            default:
                return "string";
        }
    }

    private List<EntityDef.AttributeDefsDTO> buildCommonAttribute(){
        List<EntityDef.AttributeDefsDTO> attributeDefsDTOList=new ArrayList<>();
        EntityDef.AttributeDefsDTO attributeDefsTenantDTO = new EntityDef.AttributeDefsDTO();
        EntityDef.AttributeDefsDTO attributeDefsUserDTO = new EntityDef.AttributeDefsDTO();
        EntityDef.AttributeDefsDTO attributeDefsRemarkDTO = new EntityDef.AttributeDefsDTO();
        attributeDefsTenantDTO.setName("tenantId");
        attributeDefsTenantDTO.setCardinality("SINGLE");
        attributeDefsTenantDTO.setIsOptional(true);
        attributeDefsTenantDTO.setIsUnique(false);
        attributeDefsTenantDTO.setIsIndexable(true);
        attributeDefsTenantDTO.setTypeName("string");

        attributeDefsUserDTO.setName("userId");
        attributeDefsUserDTO.setCardinality("SINGLE");
        attributeDefsUserDTO.setIsOptional(true);
        attributeDefsUserDTO.setIsUnique(false);
        attributeDefsUserDTO.setIsIndexable(true);
        attributeDefsUserDTO.setTypeName("string");


        attributeDefsRemarkDTO.setName("remark");
        attributeDefsRemarkDTO.setCardinality("SINGLE");
        attributeDefsRemarkDTO.setIsOptional(true);
        attributeDefsRemarkDTO.setIsUnique(false);
        attributeDefsRemarkDTO.setIsIndexable(true);
        attributeDefsRemarkDTO.setTypeName("string");

        attributeDefsDTOList.add(attributeDefsTenantDTO);
        attributeDefsDTOList.add(attributeDefsUserDTO);
        attributeDefsDTOList.add(attributeDefsRemarkDTO);
        return attributeDefsDTOList;
    }

    private EntityDef buildBridgeProcessEntityDef(String serviceType) {
        String name = serviceType+"_bridge";
        EntityDef entityDef = new EntityDef();
        entityDef.setName(name);
        entityDef.setDescription(name);
        entityDef.setSuperTypes(List.of("Process"));
        entityDef.setServiceType(serviceType);
        entityDef.setTypeVersion("1.1");
        entityDef.setAttributeDefs(new ArrayList<>());
        entityDef.getAttributeDefs().addAll(buildCommonAttribute());
        return entityDef;
    }

    @NoArgsConstructor
    @Data
    public static class Model{
        private List<String> enumDefs;
        private List<String> structDefs;
        private List<String> classificationDefs;
        private List<EntityDef> entityDefs;
        private List<RelationshipDef> relationshipDefs;

        public static Model defaultObj(){
            Model model=new Model();
            model.setEnumDefs(Collections.emptyList());
            model.setStructDefs(Collections.emptyList());
            model.setClassificationDefs(Collections.emptyList());
            return model;
        }
    }


    @NoArgsConstructor
    @Data
    public static class EntityDef {
        private String name;
        private String description;
        private List<String> superTypes;
        private String serviceType;
        private String typeVersion;
        private List<AttributeDefsDTO> attributeDefs;

        @NoArgsConstructor
        @Data
        public static class AttributeDefsDTO {
            private String name;
            private String typeName;
            private String cardinality;
            private Boolean isIndexable;
            private Boolean isOptional;
            private Boolean isUnique;
        }
    }

    @NoArgsConstructor
    @Data
    public static class RelationshipDef{
        private String name;
        private String serviceType;
        private String typeVersion;
        private String relationshipCategory;
        private String relationshipLabel;
        private EndDefDTO endDef1;
        private EndDefDTO endDef2;
        private String propagateTags;
        @NoArgsConstructor
        @Data
        public static class EndDefDTO {
            private String type;
            private String name;
            private Boolean isContainer;
            private String cardinality;
            private Boolean isLegacyAttribute;
        }
    }
}
